﻿using PlutoStudio.Diagnostics;
using PlutoStudio.Net.Messages;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace PlutoStudio.Net.Udp
{
    public class UpdClient:INetClient
    {
        private static TraceHelper trace = new TraceHelper("PlutoStudio.Net.Trace", "Default Trace.");
        private Socket socket;
        private byte[] buffer;
        private SocketAsyncEventArgs receiveArgs;
        private static ConcurrentBag<SocketAsyncEventArgs> socketEventArgsPool;

        public EndPoint RemoteEndPoint { get; private set; }

        public EndPoint LocalEndPoint { get; private set; }

        private static SocketAsyncEventArgs GetSocketAsyncEventArgs()
        {
            SocketAsyncEventArgs result;
            if (!socketEventArgsPool.TryTake(out result))
            {
                result = new SocketAsyncEventArgs();
            }
            return result;
        }

        private static void ReleaseSocketAsyncEventArgs(SocketAsyncEventArgs args)
        {
            args.UserToken = null;
            args.SetBuffer(null, 0, 0);
            socketEventArgsPool.Add(args);
        }

        static UpdClient()
        {
            socketEventArgsPool = new ConcurrentBag<SocketAsyncEventArgs>();
        }

        public UpdClient()
            : this(Configuration.ConfigurationManager.UdpClient.IPAddress, Configuration.ConfigurationManager.UdpClient.Port)
        {

        }

        public UpdClient(IPAddress address, int port)
            : this(new IPEndPoint(address, port))
        {

        }

        public UpdClient(IPEndPoint localEP)
        {
            receiveArgs = GetSocketAsyncEventArgs();
            receiveArgs.Completed += ReceiveCompleted;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.Bind(localEP);
            this.LocalEndPoint = socket.LocalEndPoint ;
            buffer = new byte[65536];
            receiveArgs.SetBuffer(buffer, 0, 65536);
        }

        public event ReceivedEventHandler Received;
        public event SendedEventHandler Sended;
        public event ClosedEventHandler Closed;
        public event ErrorEventHandler Error;

        void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            ProcessReceive(e);
        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {

                trace.TraceInformation("接受到来自{0}的{1}字节的数据", RemoteEndPoint, e.BytesTransferred);
                var message = Message.FromBuffer(e.Buffer);
                Array.Clear(buffer, 0, 65536);
                System.Threading.Tasks.Task.Factory.StartNew(() => OnReceived(e.RemoteEndPoint, message));
                BeginReceive();
            }
            else
            {
                trace.TraceError("来自{0}的连接发生错误：{1}", e.RemoteEndPoint, e.SocketError);
                Close();
            }
        }

        private void OnReceived(EndPoint endPoint, IMessage message)
        {
            this.RemoteEndPoint = endPoint;
            var e = new ReceivedEventArgs(endPoint, message);
            OnReceived(e);
        }

        protected virtual void OnReceived(ReceivedEventArgs e)
        {
            if (Received != null)
            {
                Received(this, e);
            }
        }

        public void BeginReceive()
        {
            bool willRaiseEvent = socket.ReceiveFromAsync(receiveArgs);
            if (!willRaiseEvent)
            {
                ProcessReceive(receiveArgs);
            }

        }

        public void Send(EndPoint remoteEP, Messages.Message message)
        {
            if (socket == null)
            {
                return;
            }
            var buffer = message.ToBytes();
            var args = GetSocketAsyncEventArgs();
            args.SetBuffer(buffer, 0, buffer.Length);
            args.Completed += SendCompleted;
            args.RemoteEndPoint = remoteEP;
            if (!socket.SendToAsync(args))
            {
                ProcessSend(args);
            }
        }

        private void ProcessSend(SocketAsyncEventArgs e)
        {
            e.Completed -= SendCompleted;
            if (e.SocketError == SocketError.Success)
            {
                trace.TraceInformation("发送{0}字节的数据", e.BytesTransferred);
            }
            else
            {
                trace.TraceError("发送数据到{0}发生错误：{1}", RemoteEndPoint, e.SocketError);
            }
            ReleaseSocketAsyncEventArgs(e);
            OnSended();
        }

        private void OnSended()
        {
            var e = EventArgs.Empty;
            OnSended(e);
        }

        protected virtual void OnSended(EventArgs e)
        {
            if (Sended != null)
            {
                Sended(this, e);
            }
        }

        void SendCompleted(object sender, SocketAsyncEventArgs e)
        {
            ProcessSend(e);
        }

        public void Close()
        {
            try
            {
                socket.Shutdown(SocketShutdown.Send);
                Trace.TraceInformation("连接已经被关闭！");
            }
            catch (Exception ex)
            {
                trace.TraceError("关闭Socket连接发生异常！\r\n   {0}", ex);
            }
            socket.Close();
            OnClosed();
            if (receiveArgs != null)
            {
                receiveArgs.Completed -= ReceiveCompleted;
                ReleaseSocketAsyncEventArgs(receiveArgs);
                receiveArgs = null;
            }
            buffer = null;
        }

        private void OnClosed()
        {
            var e = EventArgs.Empty;
            OnClosed(e);
        }

        protected virtual void OnClosed(EventArgs e)
        {
            if (Closed != null)
            {
                Closed(this, e);
            }
        }

        public event ConnectedEventHandler Connected;

        void INetClient.Send(Message message)
        {
            throw new NotImplementedException();
        }

        void INetClient.Connect()
        {
            throw new NotImplementedException();
        }

        void INetClient.Connect(IPEndPoint endPoint)
        {
            throw new NotImplementedException();
        }
    }
}
